package com.ljc.service;

import com.ljc.entity.*;
import com.ljc.exception.ConditionException;
import com.ljc.interfaces.UserCoinApi;
import com.ljc.interfaces.VideoApi;
import com.ljc.utils.FastDFSUtil;
import com.ljc.utils.IpUtil;
import eu.bitwalker.useragentutils.UserAgent;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericPreference;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class VideoService {
    @DubboReference
    private VideoApi videoApi;
    @DubboReference
    private UserCoinApi userCoinApi;
    @Resource
    private FastDFSUtil fastDFSUtil;
    @Resource
    private UserService userService;

    /**
     * 视频投稿
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    public Video addVideos(Video video) {
        Date now = new Date();
        video.setCreateTime(now);
        video = videoApi.addVideos(video);
        Long videoId = video.getId();
        List<VideoTag> videoTagList = video.getVideoTagList();
        // 对该视频的标签与视频之间建立关系
        videoTagList.forEach(item -> {
            item.setCreateTime(now);
            item.setVideoId(videoId);
        });
        // 对视频批量添加标签
        videoApi.batchAddVideoTags(videoTagList);
        return video;
    }

    /**
     * 分页查询视频列表
     *
     * @param area 视频分区
     */
    public PageResult<Video> pageListVideos(Integer size, Integer no, String area) {
        if (size == null || no == null) {
            throw new ConditionException("参数异常");
        }
        Map<String, Object> params = new HashMap<>();
        params.put("start", (no - 1) * size);
        params.put("limit", size);
        params.put("area", area);
        List<Video> videoList = new ArrayList<>();
        Integer total = videoApi.pageCountVideos(params);
        if (total > 0) {
            videoList = videoApi.pageListVideos(params);
        }
        return new PageResult<>(total, videoList);
    }

    /**
     * 视频在线播放
     */
    public void viewVideoOnlineBySlices(HttpServletRequest request, HttpServletResponse response, String url) throws Exception {
        fastDFSUtil.viewVideoOnlineBySlices(request, response, url);
    }

    /**
     * 点赞视频
     */
    public void addVideoLike(Long videoId, Long userId) {
        Video video = videoApi.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        VideoLike videoLike = videoApi.getVideoLikeByVideoIdAndUserId(videoId, userId);
        if (videoLike != null) {
            throw new ConditionException("已经赞过！");
        }
        // 当前用户没有给该视频点赞过，需要添加一条点赞记录
        videoLike = new VideoLike();
        videoLike.setVideoId(videoId);
        videoLike.setUserId(userId);
        videoLike.setCreateTime(new Date());
        videoApi.addVideoLike(videoLike);
    }

    /**
     * 取消点赞视频
     */
    public void deleteVideoLike(Long videoId, Long userId) {
        videoApi.deleteVideoLike(videoId, userId);
    }

    /**
     * 查询视频点赞数量
     */
    public Map<String, Object> getVideoLikes(Long videoId, Long userId) {
        Long count = videoApi.getVideoLikes(videoId);
        VideoLike videoLike = videoApi.getVideoLikeByVideoIdAndUserId(videoId, userId);
        boolean like = videoLike != null;
        Map<String, Object> result = new HashMap<>();
        result.put("count", count);
        result.put("like", like);
        return result;
    }

    /**
     * 收藏视频
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    public void addVideoCollection(VideoCollection videoCollection, Long userId) {
        Long videoId = videoCollection.getVideoId();
        Long groupId = videoCollection.getGroupId();
        if (videoId == null || groupId == null) {
            throw new ConditionException("参数异常！");
        }
        Video video = videoApi.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        //删除原有视频收藏
        videoApi.deleteVideoCollection(videoId, userId);
        //添加新的视频收藏
        videoCollection.setUserId(userId);
        videoCollection.setCreateTime(new Date());
        videoApi.addVideoCollection(videoCollection);
    }

    /**
     * 查询视频收藏数量
     */
    public void deleteVideoCollection(Long videoId, Long userId) {
        videoApi.deleteVideoCollection(videoId, userId);
    }

    /**
     * 查询视频收藏数量
     */
    public Map<String, Object> getVideoCollections(Long videoId, Long userId) {
        Long count = videoApi.getVideoCollections(videoId);
        // 根据视频id和用户id查询用户是否点赞过该视频
        VideoCollection videoCollection = videoApi.getVideoCollectionByVideoIdAndUserId(videoId, userId);
        boolean like = videoCollection != null;
        Map<String, Object> result = new HashMap<>();
        result.put("count", count);
        result.put("like", like);
        return result;
    }

    /**
     * 视频投币
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    public void addVideoCoins(VideoCoin videoCoin, Long userId) {
        // 参数校验
        Long videoId = videoCoin.getVideoId();
        Integer amount = videoCoin.getAmount();
        if (videoId == null) {
            throw new ConditionException("参数异常！");
        }
        Video video = videoApi.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        //查询当前登录用户是否拥有足够的硬币
        Integer userCoinsAmount = userCoinApi.getUserCoinsAmount(userId);
        userCoinsAmount = userCoinsAmount == null ? 0 : userCoinsAmount;
        if (amount > userCoinsAmount) {
            throw new ConditionException("硬币数量不足！");
        }
        //查询当前登录用户对该视频已经投了多少硬币
        VideoCoin dbVideoCoin = videoApi.getVideoCoinByVideoIdAndUserId(videoId, userId);
        //新增视频投币
        if (dbVideoCoin == null) {
            videoCoin.setUserId(userId);
            videoCoin.setCreateTime(new Date());
            videoApi.addVideoCoin(videoCoin);
        } else {
            Integer dbAmount = dbVideoCoin.getAmount();
            dbAmount += amount;
            //更新视频投币
            videoCoin.setUserId(userId);
            videoCoin.setAmount(dbAmount);
            videoCoin.setUpdateTime(new Date());
            videoApi.updateVideoCoin(videoCoin);
        }
        //更新用户当前硬币总数
        userCoinApi.updateUserCoinsAmount(userId, (userCoinsAmount - amount));
    }

    /**
     * 查询视频投币数量
     */
    public Map<String, Object> getVideoCoins(Long videoId, Long userId) {
        Long count = videoApi.getVideoCoinsAmount(videoId);
        VideoCoin videoCollection = videoApi.getVideoCoinByVideoIdAndUserId(videoId, userId);
        boolean like = videoCollection != null;
        Map<String, Object> result = new HashMap<>();
        result.put("count", count);
        result.put("like", like);
        return result;
    }

    /**
     * 添加视频评论
     */
    public void addVideoComment(VideoComment videoComment, Long userId) {
        Long videoId = videoComment.getVideoId();
        if (videoId == null) {
            throw new ConditionException("参数异常！");
        }
        Video video = videoApi.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        videoComment.setUserId(userId);
        videoComment.setCreateTime(new Date());
        videoApi.addVideoComment(videoComment);
    }

    /**
     * 分页查询视频评论
     */
    public PageResult<VideoComment> pageListVideoComments(Integer size, Integer no, Long videoId) {
        Video video = videoApi.getVideoById(videoId);
        if (video == null) {
            throw new ConditionException("非法视频！");
        }
        Map<String, Object> params = new HashMap<>();
        params.put("start", (no - 1) * size);
        params.put("limit", size);
        params.put("videoId", videoId);
        // 查询视频评论的分页数量
        Integer total = videoApi.pageCountVideoComments(params);
        List<VideoComment> list = new ArrayList<>();
        if (total > 0) {
            // 分页查询一级视频的评论
            list = videoApi.pageListVideoComments(params);
            //批量查询二级评论（首先先拿出一级视频的评论的id，作为根评论）
            List<Long> parentIdList = list.stream().map(VideoComment::getId).collect(Collectors.toList());
            List<VideoComment> childCommentList = videoApi.batchGetVideoCommentsByRootIds(parentIdList);
            //批量查询用户信息
            Set<Long> userIdList = list.stream().map(VideoComment::getUserId).collect(Collectors.toSet());
            Set<Long> replyUserIdList = childCommentList.stream().map(VideoComment::getUserId).collect(Collectors.toSet());
            Set<Long> childUserIdList = childCommentList.stream().map(VideoComment::getReplyUserId).collect(Collectors.toSet());
            userIdList.addAll(replyUserIdList);
            userIdList.addAll(childUserIdList);
            // 查询所有评论用户和被评论用户的用户信息
            List<UserInfo> userInfoList = userService.batchGetUserInfoByUserIds(userIdList);
            // 将用户id和用户信息封装成一个map
            Map<Long, UserInfo> userInfoMap = userInfoList.stream()
                    .collect(Collectors.toMap(UserInfo::getUserId, userInfo -> userInfo));
            // 遍历一级评论列表
            list.forEach(comment -> {
                Long id = comment.getId();
                List<VideoComment> childList = new ArrayList<>();
                // 为当前一级评论加上其所属的二级评论添加用户信息
                childCommentList.forEach(child -> {
                    if (id.equals(child.getRootId())) {
                        // 设置创建评论的用户信息
                        child.setUserInfo(userInfoMap.get(child.getUserId()));
                        // 设置回复评论的用户信息
                        child.setReplyUserInfo(userInfoMap.get(child.getReplyUserId()));
                        childList.add(child);
                    }
                });
                // 将相关的二级评论设置进一级评论
                comment.setChildList(childList);
                // 为一级评论加上用户信息
                comment.setUserInfo(userInfoMap.get(comment.getUserId()));
            });
        }
        return new PageResult<>(total, list);
    }

    /**
     * 获取视频详情
     */
    public Map<String, Object> getVideoDetails(Long videoId) {
        Video video = videoApi.getVideoDetails(videoId);
        Long userId = video.getUserId();
        User user = userService.getUserInfo(userId);
        UserInfo userInfo = user.getUserInfo();
        Map<String, Object> result = new HashMap<>();
        result.put("video", video);
        result.put("userInfo", userInfo);
        return result;
    }

    /**
     * 添加视频观看记录
     */
    public void addVideoView(VideoView videoView, HttpServletRequest request) {
        Long userId = videoView.getUserId();
        Long videoId = videoView.getVideoId();
        //生成clientId
        String agent = request.getHeader("User-Agent");
        // 解析请求
        UserAgent userAgent = UserAgent.parseUserAgentString(agent);
        String clientId = String.valueOf(userAgent.getId());
        String ip = IpUtil.getIP(request);
        Map<String, Object> params = new HashMap<>();
        if (userId != null) {
            // 不为null则为用户登录状态
            params.put("userId", userId);
        } else {
            // 为null则为游客模式
            params.put("ip", ip);
            params.put("clientId", clientId);
        }
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        params.put("today", sdf.format(now));
        params.put("videoId", videoId);
        //添加观看记录
        VideoView dbVideoView = videoApi.getVideoView(params);
        if (dbVideoView == null) {
            videoView.setIp(ip);
            videoView.setClientId(clientId);
            videoView.setCreateTime(new Date());
            videoApi.addVideoView(videoView);
        }
    }

    /**
     * 查询视频播放量
     */
    public Integer getVideoViewCounts(Long videoId) {
        return videoApi.getVideoViewCounts(videoId);
    }

    /**
     * 基于用户的协同推荐
     *
     * @param userId 用户id
     */
    public List<Video> recommend(Long userId) throws TasteException {
        List<UserPreference> list = videoApi.getAllUserPreference();
        //创建数据模型
        DataModel dataModel = this.createDataModel(list);
        //获取用户相似程度
        UserSimilarity similarity = new UncenteredCosineSimilarity(dataModel);
        log.info("用户1和用户2的相似程度：{}", similarity.userSimilarity(11, 12));
        //获取用户邻居
        UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
        long[] ar = userNeighborhood.getUserNeighborhood(userId);
        //构建推荐器
        Recommender recommender = new GenericUserBasedRecommender(dataModel, userNeighborhood, similarity);
        //推荐视频
        List<RecommendedItem> recommendedItems = recommender.recommend(userId, 5);
        // 提取出视频id
        List<Long> itemIds = recommendedItems.stream().map(RecommendedItem::getItemID).collect(Collectors.toList());
        return videoApi.batchGetVideosByIds(itemIds);
    }

    /**
     * 基于内容的协同推荐
     *
     * @param userId  用户id
     * @param itemId  参考内容id（根据该内容进行相似内容推荐）
     * @param howMany 需要推荐的数量
     */
    public List<Video> recommendByItem(Long userId, Long itemId, int howMany) throws TasteException {
        List<UserPreference> list = videoApi.getAllUserPreference();
        //创建数据模型
        DataModel dataModel = this.createDataModel(list);
        //获取内容相似程度
        ItemSimilarity similarity = new UncenteredCosineSimilarity(dataModel);
        GenericItemBasedRecommender genericItemBasedRecommender = new GenericItemBasedRecommender(dataModel, similarity);
        // 物品推荐相拟度，计算两个物品同时出现的次数，次数越多任务的相拟度越高
        List<Long> itemIds = genericItemBasedRecommender.recommendedBecause(userId, itemId, howMany)
                .stream()
                .map(RecommendedItem::getItemID)
                .collect(Collectors.toList());
        //推荐视频
        return videoApi.batchGetVideosByIds(itemIds);
    }

    /**
     * 构造数据模型
     */
    private DataModel createDataModel(List<UserPreference> userPreferenceList) {
        FastByIDMap<PreferenceArray> fastByIdMap = new FastByIDMap<>();
        // 获取每个用户的偏好
        Map<Long, List<UserPreference>> map = userPreferenceList.stream().collect(Collectors.groupingBy(UserPreference::getUserId));
        Collection<List<UserPreference>> list = map.values();
        // 将数据库中的数据映射到推荐引擎中
        for (List<UserPreference> userPreferences : list) {
            GenericPreference[] array = new GenericPreference[userPreferences.size()];
            for (int i = 0; i < userPreferences.size(); i++) {
                UserPreference userPreference = userPreferences.get(i);
                GenericPreference item = new GenericPreference(userPreference.getUserId(), userPreference.getVideoId(), userPreference.getValue());
                array[i] = item;
            }
            fastByIdMap.put(array[0].getUserID(), new GenericUserPreferenceArray(Arrays.asList(array)));
        }
        return new GenericDataModel(fastByIdMap);
    }

    /**
     * 通过视频id获取视频标签
     */
    public List<VideoTag> getVideoTagsByVideoId(Long videoId) {
        return videoApi.getVideoTagsByVideoId(videoId);
    }

    /**
     * 批量删除视频标签
     */
    public void deleteVideoTags(List<Long> tagIdList, Long videoId) {
        videoApi.deleteVideoTags(tagIdList, videoId);
    }

}
